﻿using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Internal.KuiHuaBaoDian.Services.Timer;
using UnityEngine.Assertions;

namespace KuiHuaBaoDian.Services.Timer {

    /// <summary>
    /// 计时器服务
    /// </summary>
    public interface ITimerService {

        float Scale { get; set; }

        TimerHandle Add(float time = 1.0f, uint count = 1, bool isIgnoreScale = true, bool isAutoKill = true, ITimer.DataDelegate onTimer = null, ITimer.DataDelegate onComplete = null, ITimer.DataDelegate onTimerStart = null);

        UniTask Start(float time = 1.0f, uint count = 1, bool isIgnoreScale = true, ITimer.DataDelegate onTimer = null, ITimer.DataDelegate onTimerStart = null, CancellationToken cancellationToken = default);

        void Remove(TimerHandle handle);

        void Reset(TimerHandle handle);

        void Pause(TimerHandle handle);

        void Resume(TimerHandle handle);

        bool IsPausing(TimerHandle handle);
    }

    /// <summary>
    /// 计时器服务
    /// </summary>
    public static class TimerService {

        /// <summary>
        /// 单例
        /// </summary>
        public static ITimerService Instance => m_Instance ??= KHBD.TypeMap.Instantiate<ITimerService>();
        private static ITimerService m_Instance;
    }

    [Implement(typeof(ITimerService))]
    internal sealed class KHBD_TimerService : ITimerService, ITimerServiceInternal, IService {

        private readonly Stack<ITimer> m_Pool = new();
        private readonly IDictionary<TimerHandle, ITimer> m_Dictionary = new KHBD_SortedDictionary<TimerHandle, ITimer>();
        private readonly IDictionary<TimerHandle, ITimer> m_AddingDictionary = new KHBD_SortedDictionary<TimerHandle, ITimer>();
        private readonly HashSet<TimerHandle> m_RemovingSet = new();

        public float Scale { get; set; } = 1.0f;

        private bool m_IsUpdating = false;

        void IService.OnApplicationQuit() {
            m_Pool.Clear();
            m_Dictionary.Clear();
            m_AddingDictionary.Clear();
            m_RemovingSet.Clear();
        }

        public TimerHandle Add(float time = 1.0f, uint count = 1, bool isIgnoreScale = true, bool isAutoKill = true, ITimer.DataDelegate onTimer = null, ITimer.DataDelegate onComplete = null, ITimer.DataDelegate onTimerStart = null) {
            var timer = AddTimer(time, count, isIgnoreScale, isAutoKill, onTimer, onComplete, onTimerStart);
            return timer.Handle;
        }

        private ITimer AddTimer(float time = 1.0f, uint count = 1, bool isIgnoreScale = true, bool isAutoKill = true, ITimer.DataDelegate onTimer = null, ITimer.DataDelegate onComplete = null, ITimer.DataDelegate onTimerStart = null) {
            time = time >= 0 ? time : 0.0f;
            if (m_Pool.TryPop(out var timer)) {
                var reusable = (ITimerServiceInternal.IReusable)timer;
                reusable.Reuse();
            } else {
                timer = KHBD.TypeMap.Instantiate<ITimer>();
                Assert.IsTrue(timer is ITimerServiceInternal.IReusable, $"[{typeof(ITimer).FullName}] 的实现 [{timer.GetType().FullName}] 必须同时实现 [{typeof(ITimerServiceInternal.IReusable).FullName}]");
            }
            if (timer is ITimerSetter setter) {
                setter.SetTime(time);
                setter.SetCount(count);
                setter.SetIsIgnoreScale(isIgnoreScale);
                setter.SetIsAutoKill(isAutoKill);
            }

            timer.Timer += data => onTimer?.Invoke(data);
            timer.Completed += data => onComplete?.Invoke(data);
            timer.TimerStart += data => onTimerStart?.Invoke(data);

            if (!m_IsUpdating) {
                m_Dictionary.Add(timer.Handle, timer);
            } else {
                m_AddingDictionary.Add(timer.Handle, timer);
            }
            timer.Start();
            return timer;
        }

        public async UniTask Start(float time = 1.0f, uint count = 1, bool isIgnoreScale = true, ITimer.DataDelegate onTimer = null, ITimer.DataDelegate onTimerStart = null, CancellationToken cancellationToken = default) {
            var timer = AddTimer(time, count, isIgnoreScale, true, onTimer, null, onTimerStart);
            if (timer != null) {
                await KHBD.Context.WaitUntil(() => timer.IsComplete, cancellationToken);
                if (KHBD.Context.IsCancellationRequested(cancellationToken)) {
                    Remove(timer.Handle);
                }
            }
        }

        public void Remove(TimerHandle handle) {
            if (m_AddingDictionary.TryGetValue(handle, out var timer)) {
                m_AddingDictionary.Remove(handle);
            } else {
                if (!m_IsUpdating) {
                    if (m_Dictionary.ContainsKey(handle)) {
                        m_Dictionary.Remove(handle);
                    }
                } else {
                    if (m_Dictionary.ContainsKey(handle)) {
                        m_RemovingSet.Add(handle);
                    }
                }
            }
            if (timer is ITimerServiceInternal.IReusable reusable) {
                reusable.Recycle();
                m_Pool.Push(timer);
            }
        }

        public void Reset(TimerHandle handle) {
            if (m_Dictionary.TryGetValue(handle, out var timer)) {
                timer.Reset();
                return;
            }
            throw new InvalidOperationException();
        }

        public void Pause(TimerHandle handle) {
            if (m_Dictionary.TryGetValue(handle, out var timer)) {
                (timer as ITimerSetter).SetIsPause(true);
                return;
            }
            throw new InvalidOperationException();
        }

        public void Resume(TimerHandle handle) {
            if (m_Dictionary.TryGetValue(handle, out var timer)) {
                (timer as ITimerSetter).SetIsPause(false);
                return;
            }
            throw new InvalidOperationException();
        }

        public bool IsPausing(TimerHandle handle) {
            if (m_Dictionary.TryGetValue(handle, out var timer)) {
                return timer.IsPausing;
            }
            throw new InvalidOperationException();
        }

        void ITimerServiceInternal.Update(float delta) {
            m_IsUpdating = true;
            var scale = Scale >= 0 ? Scale : 0;
            foreach (var kv in m_Dictionary) {
                var timer = kv.Value;
                var timerDelta = delta * (timer.IsIgnoreScale ? 1.0f : scale);
                timer.Update(timerDelta);
                if (timer.IsComplete && timer.IsAutoKill) {
                    Remove(timer.Handle);
                }
            }
            m_IsUpdating = false;
            foreach (var removing in m_RemovingSet) {
                m_Dictionary.Remove(removing);
            }
            m_RemovingSet.Clear();
            foreach (var adding in m_AddingDictionary) {
                m_Dictionary.Add(adding.Key, adding.Value);
            }
            m_AddingDictionary.Clear();
        }
    }
}

namespace Internal.KuiHuaBaoDian.Services.Timer {

    public interface ITimerServiceInternal {

        public interface IReusable {

            void Reuse();

            void Recycle();
        }

        void Update(float delta);
    }
}
